﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Text;
using System.Windows.Forms;

namespace VisualTrajectory
{
    public partial class TrajectoryViewPanel : Panel
    {
        // Trajectory Datas
        private TrajectoryCollection m_Trajs = null;
        private List<List<Point>> m_ProjLines = null;
        private RectangleF m_TrajsRect = new RectangleF();

        // View Port
        private int m_nBorderSize = 18;
        private Point m_ViewPos = new Point(0, 0);
        private Size m_ViewSize = new Size(0, 0);
        private bool m_bAutoSetViewPort = true;

        // Grid & Ruler
        private bool m_bShowGrid = true;
        private int  m_nGridSize = 40;

        // Mouse Control
        private bool m_bEnableMouseControl = true;
        private bool m_bTrajectorySelectMode = false;
        private List<int> m_HighlightedTrajIndexes = new List<int>();
        private int  m_nSelectedTrajIndex = -1;
        private Point m_LastMousePoint = new Point();
        private bool m_bMouseLeftButtonDown = false;
        private Cursor m_ViewPortMoveHandCursor = null;
        private Cursor m_ViewPortCaptureHandCursor = null;
        private Cursor m_ViewPortSelectCursor = null;


        public TrajectoryViewPanel()
        {
            InitializeComponent();
            m_ViewPortMoveHandCursor = new Cursor(GetType(), "Hand1.cur");
            m_ViewPortCaptureHandCursor = new Cursor(GetType(), "Hand2.cur");
            m_ViewPortSelectCursor = Cursors.Default;
            this.Cursor = m_ViewPortMoveHandCursor;
        }

        public void SetTrajectories(TrajectoryCollection trajs)
        {
            m_Trajs = trajs;
            FindTrajectoriesRect();
            ProjectTrajectories();
        }

        public int GetSelectedTrajectory()
        {
            return m_nSelectedTrajIndex;
        }

        public void SetSelectedTrajectory(int nIndex)
        {
            m_nSelectedTrajIndex = nIndex;
        }

        public void SetHighlightedTrajectories(List<int> indexes)
        {
            m_HighlightedTrajIndexes.Clear();
            m_HighlightedTrajIndexes = new List<int>(indexes);
        }
       

        public void SetBorderSize(int nBorderSize)
        {
            m_nBorderSize = nBorderSize;
        }

        public int GetBorderSize()
        {
            return m_nBorderSize;
        }

        public void SetAutoSetViewPort(bool bAuto)
        {
            m_bAutoSetViewPort = bAuto;
        }

        public void SetViewPos(Point pos)
        {
            m_ViewPos = pos;
        }

        public Point GetViewPos()
        {
            return m_ViewPos;
        }

        public void SetViewSize(Size size)
        {
            m_ViewSize = size;
        }

        public Size GetViewSize()
        {
            return m_ViewSize;
        }

        public void SetShowGrid(bool bShow)
        {
            m_bShowGrid = bShow;
        }

        public void SetGridSize(int nSize)
        {
            m_nGridSize = nSize;
        }

        public bool IsEmpty()
        {
            return m_Trajs == null || m_Trajs.Count() == 0;
        }

        public void SetTrajectorySelectMode(bool bSel)
        {
            m_bTrajectorySelectMode = bSel;
            if (m_bTrajectorySelectMode)
            {
                this.Cursor = m_ViewPortSelectCursor;
            }
            else
            {
                this.Cursor = m_ViewPortMoveHandCursor;
            }
        }

        public void ReProject()
        {
            ProjectTrajectories();
        }

        private void TrajectoryViewPanel_Paint(object sender, PaintEventArgs e)
        {
            if (IsEmpty())
                return;

            DrawTrajectories(e.Graphics);

            if (m_bShowGrid)
                DrawGrids(e.Graphics);

            if (m_bEnableMouseControl)
                DrawSelectedTrajHighlight(e.Graphics);

            DrawHighlightedTrajs(e.Graphics);
        }

        private void DrawTrajectories(Graphics g)
        {
            int nLineIndex;
            g.SetClip(GetPaintAreaRect());
            for (nLineIndex = 0; nLineIndex < m_ProjLines.Count; nLineIndex++)
            {
                List<Point> line = m_ProjLines[nLineIndex];
                Point[] points = line.ToArray();
                g.DrawLines(Pens.Green, points);
            }
            g.SetClip(new Rectangle(0, 0, this.Size.Width, this.Size.Height));
        }

        private void DrawSelectedTrajHighlight(Graphics g)
        {
            if (m_nSelectedTrajIndex == -1)
                return;

            List<Point> line = m_ProjLines[m_nSelectedTrajIndex];
            Point[] points = line.ToArray();
            g.DrawLines(Pens.Blue, points);
            Rectangle flagRect = new Rectangle(0,0,10,10);
            for (int i = 0; i < points.Length; i++)
            {
                flagRect.X = points[i].X-5;
                flagRect.Y = points[i].Y-5;
                if (i == points.Length - 1)
                {
                    g.DrawRectangle(Pens.Red, flagRect);
                }
                else
                {
                    g.DrawRectangle(Pens.Blue, flagRect);
                }
            }
        }

        private void DrawHighlightedTrajs(Graphics g)
        {
            for (int traj = 0; traj < m_HighlightedTrajIndexes.Count; traj++)
            {
                List<Point> line = m_ProjLines[m_HighlightedTrajIndexes[traj]];
                Point[] points = line.ToArray();
                g.DrawLines(Pens.Red, points);
                Rectangle flagRect = new Rectangle(0, 0, 10, 10);
                for (int i = 0; i < points.Length; i++)
                {
                    flagRect.X = points[i].X - 5;
                    flagRect.Y = points[i].Y - 5;
                    if (i == points.Length - 1)
                    {
                        g.DrawRectangle(Pens.Red, flagRect);
                    }
                }
            }
        }

        private void DrawGrids(Graphics g)
        {
            // Create grid pen
            Pen gridPen = new Pen(Color.DarkGray, 1.0f);
            gridPen.DashStyle = System.Drawing.Drawing2D.DashStyle.Dot;
            
            // Calculate the grid size of pixels 
            Rectangle paintRect = GetPaintAreaRect();
            float widthRatio = ((float)m_ViewSize.Width) / m_TrajsRect.Width;
            float heightRatio = ((float)m_ViewSize.Height) / m_TrajsRect.Height;
            float fGridInTrajWidth = ((float)m_nGridSize) / widthRatio;
            float fGridInTrajHeight = ((float)m_nGridSize) / heightRatio;

            // Draw grid lines
            int x, y;
            Point p1 = new Point();
            Point p2 = new Point();
            p1.X = paintRect.X;
            p2.X = paintRect.X + paintRect.Width;
            float fRulerNum;
            fRulerNum = m_TrajsRect.Y + m_ViewPos.Y / heightRatio;
            Font font = new System.Drawing.Font("Arial", 10);
            for (y = paintRect.Y; y < paintRect.Y + paintRect.Height; y+=m_nGridSize)
            {
                p1.Y = y;
                p2.Y = y;
                g.DrawLine(gridPen, p1, p2);
                g.DrawString(fRulerNum.ToString("F1"), font, Brushes.Black, p1.X - m_nBorderSize, p1.Y - m_nBorderSize / 2);
                fRulerNum += fGridInTrajHeight;
            }
            p1.Y = paintRect.Y;
            p2.Y = paintRect.Y + paintRect.Height;
            fRulerNum = m_TrajsRect.X + m_ViewPos.X / widthRatio;
            for (x = paintRect.X; x < paintRect.X + paintRect.Width; x +=m_nGridSize)
            {
                p1.X = x;
                p2.X = x;
                g.DrawLine(gridPen, p1, p2);
                g.DrawString(fRulerNum.ToString("F1"), font, Brushes.Black, p1.X - m_nBorderSize / 2, p1.Y - m_nBorderSize);
                fRulerNum += fGridInTrajWidth;
            }
            // Dispose the grid pen
            gridPen.Dispose();
        }

        private void FindTrajectoriesRect()
        {
            m_TrajsRect = new RectangleF(float.MaxValue, float.MaxValue, 0.0f, 0.0f);
            int nTrajIndex;
            int nPointIndex;
            for (nTrajIndex = 0; nTrajIndex < m_Trajs.Count(); nTrajIndex++)
            {
                Trajectory traj = m_Trajs.GetTrajectoryByIndex(nTrajIndex);
                for (nPointIndex = 0; nPointIndex < traj.GetPointCount(); nPointIndex++)
                {
                    PointF p = traj.GetPoint(nPointIndex);
                    if (m_TrajsRect.X > p.X)
                    {
                        m_TrajsRect.X = p.X;
                    }
                    if (m_TrajsRect.Y > p.Y)
                    {
                        m_TrajsRect.Y = p.Y;
                    }
                    if (m_TrajsRect.Width < p.X)
                    {
                        m_TrajsRect.Width = p.X;
                    }
                    if (m_TrajsRect.Height < p.Y)
                    {
                        m_TrajsRect.Height = p.Y;
                    }
                }
            }
            m_TrajsRect.Width  -= m_TrajsRect.X;
            m_TrajsRect.Height -= m_TrajsRect.Y;
        }

        public void AutoSetViewPort()
        {
            Rectangle paintRect = GetPaintAreaRect();
            m_ViewPos.X = 0;
            m_ViewPos.Y = 0;
            m_ViewSize.Width = paintRect.Width;
            m_ViewSize.Height = paintRect.Height;
        }

        private Rectangle GetPaintAreaRect()
        {
            return new Rectangle(m_nBorderSize, m_nBorderSize, this.Size.Width - 2 * m_nBorderSize, this.Size.Height - 2 * m_nBorderSize);
        }

        private void ProjectTrajectories()
        {
            if (IsEmpty())
                return;

            if (m_bAutoSetViewPort)
                AutoSetViewPort();

            Rectangle paintRect = GetPaintAreaRect();
            float widthRatio = ((float)m_ViewSize.Width) / m_TrajsRect.Width;
            float heightRatio = ((float)m_ViewSize.Height) / m_TrajsRect.Height;
            m_ProjLines = new List<List<Point>>(m_Trajs.Count());
            int nLineIndex;
            for (nLineIndex = 0; nLineIndex < m_Trajs.Count(); nLineIndex++)
            {
                Trajectory traj = m_Trajs.GetTrajectoryByIndex(nLineIndex);
                List<Point> line = new List<Point>(traj.GetPointCount());
                int nPointIndex;
                for (nPointIndex = 0; nPointIndex < traj.GetPointCount(); nPointIndex++)
                {
                    PointF trajP = traj.GetPoint(nPointIndex);
                    Point projP = new Point();
                    projP.X = m_nBorderSize + ((int)((trajP.X - m_TrajsRect.X)* widthRatio)) - m_ViewPos.X;
                    projP.Y = m_nBorderSize + ((int)((trajP.Y - m_TrajsRect.Y)* heightRatio)) - m_ViewPos.Y;
                    line.Add(projP);
                }
                m_ProjLines.Add(line);
            }
        }

        private void TrajectoryViewPanel_Resize(object sender, EventArgs e)
        {
            ProjectTrajectories();
            this.Invalidate();
        }

        private void TrajectoryViewPanel_SizeChanged(object sender, EventArgs e)
        {
            ProjectTrajectories();
            this.Invalidate();
        }

        private int FindClosetTrajectory(Point mousePoint, ref double dDist)
        {
            int nTrajIndex;
            int nSubIndex;
            double dMinDist = double.MaxValue;
            int nSelectedTrajIndex = -1;
            
            mousePoint.X -= m_nBorderSize;
            mousePoint.Y -= m_nBorderSize;

            for (nTrajIndex = 0; nTrajIndex < m_ProjLines.Count; nTrajIndex++)
            {
                List<Point> TrajLine = m_ProjLines[nTrajIndex];
                for (nSubIndex = 0; nSubIndex < TrajLine.Count - 1; nSubIndex++)
                {
                    Point p1 = TrajLine[nSubIndex];
                    Point p2 = TrajLine[nSubIndex + 1];
                    double dist = CalculateSquaredDist(p1, p2, mousePoint);
                    if (dist < dMinDist)
                    {
                        nSelectedTrajIndex = nTrajIndex;
                        dMinDist = dist;
                    }
                }
            }
            dDist = dMinDist;
            return nSelectedTrajIndex;
        }

        /// <summary>
        /// Calculate the squared distance from a point to a line segment
        /// If the perpendicular point is out of line segment, return the nearest distance point to two end points of the line segment.
        /// PS : This is the Fastest Method Proposed in Graphics Computing
        /// </summary>
        /// <param name="a">endpoint of the line</param>
        /// <param name="b">endpoint of the line</param>
        /// <param name="c">point not in the line</param>
        /// <returns></returns>
        private double CalculateSquaredDist(Point a, Point b, Point c)
        {
            Point ab = new Point(b.X - a.X, b.Y - a.Y); // ab = b - a
            Point ac = new Point(c.X - a.X, c.Y - a.Y); // ac = c - a
            float f = ab.X * ac.X + ab.Y * ac.Y; // f = |ab||ac|cos(theta)
            if (f < 0.0f) // > 90 degree
                return (c.X - a.X) * (c.X - a.X) + (c.Y - a.Y) * (c.Y - a.Y); // return dist(a, c)
            float d = ab.X * ab.X + ab.Y * ab.Y; // d = |ab|*|ab|
            if (f > d) // |ac|cos(theta) > |ab|
                return (c.X - b.X) * (c.X - b.X) + (c.Y - b.Y) * (c.Y - b.Y); // return dist(b, c)
            f = f / d; // f = |ac|/|ab| * cos(theta)
            PointF D = new PointF(a.X + f * ab.X, a.Y + f * ab.Y); // D = a + f*ab
            return (c.X - D.X) * (c.X - D.X) + (c.Y - D.Y) * (c.Y - D.Y); // return dist(c, D)
         }

        private void TrajectoryViewPanel_MouseClick(object sender, MouseEventArgs e)
        {
            if (m_bEnableMouseControl)
            {
                if (m_bTrajectorySelectMode)
                {
                    if (e.Button == MouseButtons.Left)
                    {
                        double dDist = 0.0;
                        m_nSelectedTrajIndex = FindClosetTrajectory(new Point(e.X, e.Y), ref dDist);
                    }
                    else
                    {
                        m_nSelectedTrajIndex = -1;
                    }
                    this.Invalidate();
                }
            }
            
        }

        private void TrajectoryViewPanel_MouseDoubleClick(object sender, MouseEventArgs e)
        {

        }

        private void TrajectoryViewPanel_MouseUp(object sender, MouseEventArgs e)
        {
            if (m_bEnableMouseControl)
            {
                if (m_bTrajectorySelectMode == false)
                {
                    if (e.Button == MouseButtons.Left)
                    {
                        m_bMouseLeftButtonDown = false;
                        this.Cursor = m_ViewPortMoveHandCursor;
                    }
                }
            }
        }

        private void TrajectoryViewPanel_MouseMove(object sender, MouseEventArgs e)
        {
            if (m_bEnableMouseControl)
            {
                if (m_bTrajectorySelectMode == false)
                {
                    if (e.Button == MouseButtons.Left && m_bMouseLeftButtonDown)
                    {
                        // Drag the view port
                        m_ViewPos.X -= e.X - m_LastMousePoint.X;
                        m_ViewPos.Y -= e.Y - m_LastMousePoint.Y;
                        m_LastMousePoint.X = e.X;
                        m_LastMousePoint.Y = e.Y;
                        this.ReProject();
                        this.Invalidate();
                    }
                }
            }
        }

        private void TrajectoryViewPanel_MouseDown(object sender, MouseEventArgs e)
        {
            if (m_bEnableMouseControl)
            {
                if (m_bTrajectorySelectMode == false)
                {
                    if (e.Button == MouseButtons.Left)
                    {
                        // Drag the view port
                        m_bMouseLeftButtonDown = true;
                        m_LastMousePoint.X = e.X;
                        m_LastMousePoint.Y = e.Y;
                        this.Cursor = m_ViewPortCaptureHandCursor;
                    }
                }
            }
        }


    }
}
